package com.remoter.api.context.support;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.remoter.api.cluster.ILoadBalance;
import com.remoter.api.configure.IConfiguration;
import com.remoter.api.configure.support.AbstractConfiguration;
import com.remoter.api.consumer.IConsumerService;
import com.remoter.api.context.IContextService;
import com.remoter.api.context.IModule;
import com.remoter.api.exception.ProviderNotFoundException;
import com.remoter.api.extension.support.ExtensionLoader;
import com.remoter.api.monitor.IMonitorService;
import com.remoter.api.provider.IProviderService;
import com.remoter.api.registry.IRegistry;
import com.remoter.api.registry.IRegistryListener;
import com.remoter.api.util.Final;
import com.remoter.api.util.Guid;
import com.remoter.api.util.Proxy;
import com.remoter.api.util.StringUtil;

public abstract class AbstractContextService implements IContextService{
	
	protected static final Logger logger = LoggerFactory.getLogger(AbstractContextService.class);
	protected static final ConcurrentHashMap<String,Exporter> exporters = new ConcurrentHashMap<String,Exporter>();
	protected static final ConcurrentHashMap<String,Importer> importers = new ConcurrentHashMap<String,Importer>();
	protected static final ConcurrentHashMap<String,IModule> services = new ConcurrentHashMap<String,IModule>();
	
	protected IConfiguration configuration;
	protected IRegistry registry;
	protected IProviderService providerService;
	protected IConsumerService consumerService;
	protected InetSocketAddress bind;
	protected InetSocketAddress local;
	protected String defaultLoadBalance;
	protected IMonitorService monitorService;
	
	protected IRegistryListener registryListener = new IRegistryListener(){
		@Override
		public void onUpdate(String path,Object data){
			if(StringUtil.isBlank(path) || null == data || !(data instanceof IModule)){
				return;
			}
			IModule module = (IModule)data;
			services.put(module.getPath(),module);
			logger.debug("node update for : " + path);
		}
		@Override
		public void onDelete(String path,Object data){
			if(StringUtil.isBlank(path) || null == data || !(data instanceof IModule)){
				return;
			}
			IModule module = (IModule)data;
			services.remove(module.getPath());
			logger.debug("node delete for : " + path);
		}
		@Override
		public void onCreate(String path, Object data){
			if(StringUtil.isBlank(path) || null == data || !(data instanceof IModule)){
				return;
			}
			IModule module = (IModule)data;
			services.put(module.getPath(),module);
			logger.debug("node create for : " + path);
		}
	};
	
	public AbstractContextService(){
		exporters.clear();
		importers.clear();
		services.clear();
		this.configuration = AbstractConfiguration.getConfiguration();
		this.registry = ExtensionLoader.getService(IRegistry.class,this.configuration.getOption(Final.O_SERVER_REGISTRY));
		this.providerService = ExtensionLoader.getService(IProviderService.class,this.configuration.getOption(Final.O_SERVER_PROVIDER));
		this.consumerService = ExtensionLoader.getService(IConsumerService.class,this.configuration.getOption(Final.O_SERVER_CONSUMER));
		this.registry.attachListener(this.registryListener);
		this.bind = this.providerService.getBindSocketAddress();
		this.local = this.consumerService.getBindSocketAddress();
		this.defaultLoadBalance = this.getDefaultLoadBalance();
	}
	
	@Override
	public synchronized void start(){
		this.initServices();
		this.onStart();
		this.startMonitor();
	}

	@Override
	public synchronized void close(){
		for(Entry<String,Exporter> entry : exporters.entrySet()){
			this.registry.detachExporter(entry.getValue());
		}
		for(Entry<String,Importer> entry : importers.entrySet()){
			this.registry.detachImporter(entry.getValue());
		}
		exporters.clear();
		importers.clear();
		services.clear();
		if(null != this.registry){
			this.registry.close();
		}
		if(null != this.providerService){
			this.providerService.unBind();
		}
		if(null != this.consumerService){
			this.consumerService.destory();
		}
		this.onClose();
		this.closeMonitor();
	}

	@Override
	public synchronized void attachExporter(Exporter exporter){
		if(null == exporter){
			throw new IllegalArgumentException("exporter is null");
		}
		try{
			String path = exporter.getPath();
			logger.debug("attach exporter for :" + path);
			if(exporters.containsKey(path)){
				logger.warn(path +" was exists");
				return;
			}
			if(this.registry.attachExporter(exporter)){
				exporters.putIfAbsent(path,exporter);
				this.providerService.bind(this.bind);
			}else{
				logger.warn("attachExporter fail for :" + path);
			}
		}catch(Exception e){
			logger.error(e.getMessage(),e);
		}
	}
	
	@Override
	public Exporter getExporter(String beanName,Class<?> type){
		for(Entry<String,Exporter> entry : exporters.entrySet()){
			Exporter exporter = entry.getValue();
			if(StringUtil.isEquals(beanName,exporter.getBean()) && exporter.getType().equals(type)){
				return exporter;
			}
		}
		return null;
	}
	
	@Override
	public synchronized void attachImporter(Importer importer){
		if(null == importer){
			throw new IllegalArgumentException("importer is null");
		}
		try{
			String path = importer.getPath();
			if(importers.containsKey(path)){
				logger.warn(path +" was exists");
				return;
			}
			if(this.registry.attachImporter(importer)){
				importer.setProxy(Proxy.getProxy(importer.getType()).newInstance(new ContextInvocationHandler(this,this.consumerService,importer)));
				importers.putIfAbsent(path,importer);
				if(importer.isCheck()){
					this.consumerService.connect(importer.getSocketAddress());
				}
			}
		}catch(Exception e){
			logger.error(e.getMessage(),e);
		}
	}
	
	@Override
	public Importer getImporter(String beanName,Class<?> type) {
		for(Entry<String,Importer> entry : importers.entrySet()){
			Importer importer = entry.getValue();
			if(StringUtil.isEquals(beanName,importer.getBean()) && importer.getType().equals(type)){
				return importer;
			}
		}
		return null;
	}

	@Override
	public synchronized Exporter createExporter(String beanName, Class<?> type,Object instance){
		String server = this.configuration.getOption(Final.O_SERVER_NAME);
		Exporter exporter = AbstractModule.createExporter(server,this.bind.getHostString(),this.bind.getPort(),beanName,type);
		exporter.setInstance(instance);
		return exporter;
	}

	@Override
	public synchronized Importer createImporter(String beanName, Class<?> type) {
		String server = this.configuration.getOption(Final.O_SERVER_NAME);
		Importer importer = AbstractModule.createImporter(server,this.local.getHostString(),this.local.getPort(),beanName,type);
		return importer;
	}

	@Override
	public synchronized void detachExporter(String beanName,Class<?> type){
		for(Entry<String,Exporter> entry : exporters.entrySet()){
			Exporter exporter = entry.getValue();
			if(StringUtil.isEquals(beanName,exporter.getBean()) && exporter.getType().equals(type)){
				exporters.remove(entry.getKey());
				this.registry.detachExporter(exporter);
			}
		}
	}

	@Override
	public synchronized void detachImporter(String beanName,Class<?> type){
		for(Entry<String,Importer> entry : importers.entrySet()){
			Importer importer = entry.getValue();
			if(StringUtil.isEquals(beanName,importer.getBean()) && importer.getType().equals(type)){
				importers.remove(entry.getKey());
				this.registry.detachImporter(importer);
			}
		}
	}
	
	@Override
	public Exporter select(Importer importer) throws ProviderNotFoundException{
		String loadBalanceName = importer.getLoadbalance();
		if(StringUtil.isBlank(loadBalanceName)){
			loadBalanceName = this.defaultLoadBalance;
		}
		ILoadBalance loadBalance = ExtensionLoader.getService(ILoadBalance.class,loadBalanceName);
		List<Exporter> exporters = this.getExporters(importer);
		if(null == exporters || exporters.size() == 0){
			throw ProviderNotFoundException.INSTANCE;
		}
		if(null == loadBalance){
			logger.warn("load balance not found for :" + loadBalanceName);
			return exporters.get(0);
		}
		return loadBalance.select(importer,exporters);
	}
	
	protected void initServices(){
		List<Exporter> exps = this.registry.exporters();
		if(null != exps && exps.size() > 0){
			for(Exporter exporter : exps){
				String key = exporter.getPath();
				if(!services.containsKey(key)){
					services.put(key,exporter);
					logger.info("load exporter for : " + key);
				}
			}
		}
	}
	
	protected List<Exporter> getExporters(Importer importer){
		List<Exporter> result = new ArrayList<Exporter>();
		for(Entry<String,IModule> entry : services.entrySet()){
			IModule module = entry.getValue();
			if(module.isExporter()){
				Exporter e2 = module.parseModule(Exporter.class);
				if(this.isSameExporter(e2,importer)){
					result.add(e2);
				}
			}
		}
		return result;
	}
	
	protected boolean isSameExporter(Exporter e1,Importer importer){
		if(null == e1 || null == importer){
			return false;
		}
		if(StringUtil.isEquals(e1.getBean(),importer.getBean()) && e1.getType().equals(importer.getType())){
			return true;
		}else{
			return false;
		}
	}
	
	protected void startMonitor(){
		String monitor = this.configuration.getOption(Final.O_SERVER_MONITOR);
		if(StringUtil.isBlank(monitor)){
			return;
		}
		String name = this.configuration.getOption(Final.O_SERVER_NAME) + Guid.get();
		this.monitorService = ExtensionLoader.getService(IMonitorService.class,monitor);
		this.monitorService.start(this,this.providerService,this.consumerService);
		Exporter exporter = this.createExporter(name,IMonitorService.class,this.monitorService);
		this.attachExporter(exporter);
	}
	
	protected void closeMonitor(){
		if(null == this.monitorService){
			return;
		}
		this.monitorService.close();
		this.detachExporter(this.configuration.getOption(Final.O_SERVER_NAME),IMonitorService.class);
	}
	
	@Override
	public ConcurrentHashMap<String, Exporter> getExporters() {
		return exporters;
	}

	@Override
	public ConcurrentHashMap<String, Importer> getImporters() {
		return importers;
	}

	@Override
	public ConcurrentHashMap<String, IModule> getServices() {
		return services;
	}

	protected abstract void onStart();
	protected abstract void onClose();
	
}